/************************************************************************
* image_png.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#include "graphics.h"
#include "file.h"
#include "path.h"

#include <string.h>
#include <png.h>

static void user_read_fn(png_structp png_ptr, png_bytep buffer, png_size_t size)
{
	file_t *data = png_get_io_ptr(png_ptr);
	if( data->len && (data->pos + size) > data->len )
	{
		png_error(png_ptr, "read error loading image");
		return;
	}

	memcpy(buffer, data->data + data->pos, size);
	data->pos += size;
}

/* is it a png image? */
int image_is_png(file_t *f)
{
	return png_check_sig(f->data, 4);
}

/* load a png image to pixel data */
int image_load_png(file_t *f, image_t *p)
{
	png_structp png_ptr;
	png_infop info_ptr;
	unsigned int sig_read = 0;
	png_uint_32 width, height;
	int bit_depth;
	int color_type;
	int interlace_type;
	int i;
	int rowbytes;
	int bpp;
	png_bytep *row_pointers;

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (png_ptr == NULL)
		return 1;

	png_set_error_fn(png_ptr, NULL, NULL, NULL);
	png_set_read_fn(png_ptr, (png_voidp)f, user_read_fn);
	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) {
		png_destroy_read_struct(&png_ptr, NULL, NULL);
		return 1;
	}

	/* this is libpng's error handling, if something goes bad we'll
	 * end up jumping back here where we can exit gracefullyish */
	if (setjmp(png_jmpbuf(png_ptr))) {
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		return 1;
	}

	png_init_io(png_ptr, (FILE*)f);
	png_set_sig_bytes(png_ptr, sig_read);
	png_read_info(png_ptr, info_ptr);
	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL);

	p->w = width;
	p->h = height;

	if (color_type == PNG_COLOR_TYPE_PALETTE)
		png_set_expand(png_ptr);
	if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
		png_set_expand(png_ptr);
	if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
		png_set_expand(png_ptr);

	png_read_update_info(png_ptr, info_ptr);

	/* get the size of image rows in bytes */
	rowbytes = png_get_rowbytes(png_ptr, info_ptr);
	bpp = rowbytes/width;

	p->pixels = malloc(sizeof(png_byte)*(rowbytes * height));
	if (!p->pixels) {
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		return 1;
	}

	/* libpng reads by row, not by chunk, so we need row pointers to load to */
	row_pointers = alloca(sizeof(png_bytep)*height);
	if (!row_pointers) {
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		free(p->pixels);
		return 1;
	}

	/* and this ensures the rows point to the correct place in the pixel data */
	for (i=0; i < height; ++i) {
		row_pointers[i] = p->pixels + i * rowbytes;
	}

	/* finally let libpng read everything in */
	png_read_image(png_ptr, row_pointers);

	png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

	/* check for a 32bit RGBA image */
	if (bpp != 4) {
		/* convert 24bit RGB to 32bit RGBA */
		if (bpp == 3) {
			int j = 0;
			int m = rowbytes*height;
			unsigned char *px = p->pixels;
			p->pixels = malloc(p->w*p->h*4);
			for (i=0; i<m; i+=3) {
				p->pixels[j++] = px[i];
				p->pixels[j++] = px[i+1];
				p->pixels[j++] = px[i+2];
				p->pixels[j++] = 255;
			}
			free(px);
		/* unsupported image */
		}else{
			vlprintf(CN_INFO, "PNG: unsupported image format: %dbpp",bpp);
			free(p->pixels);
			return 1;
		}
	}

	return 0;
}

/* write pixel data to a png image */
int image_save_png(image_t *p, char* file)
{
	char buff[2048];
	FILE *f;
	int i;
	int rb;
	png_structp png_ptr;
	png_infop info_ptr;
	png_bytep *row_pointers;

	if (!path_get(NULL,file,0,buff,2048))
		return 1;
	f = fopen(buff,"wb");
	if (!f)
		return 1;

	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
	if (!png_ptr) {
		fclose(f);
		return 1;
	}

	/* Allocate/initialize the image information data.  REQUIRED */
	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) {
		fclose(f);
		png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
		return 1;
	}

	/* this is libpng's error handling, if something goes bad we'll
	 * end up jumping back here where we can exit gracefullyish */
	if (setjmp(png_jmpbuf(png_ptr))) {
		fclose(f);
		png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
		return 1;
	}

	png_init_io(png_ptr, f);

	png_set_compression_level(png_ptr, 9);

	png_set_IHDR(
		png_ptr,
		info_ptr,
		p->w,
		p->h,
		8,
		PNG_COLOR_TYPE_RGB_ALPHA,
		PNG_INTERLACE_NONE,
		PNG_COMPRESSION_TYPE_BASE,
		PNG_FILTER_TYPE_BASE
	);

	/* Write the file header information */
	png_write_info(png_ptr, info_ptr);

	/* create rows for later writing */
	row_pointers = alloca(sizeof(png_bytep)*p->h);
	rb = p->w*4;
	if (!row_pointers) {
		png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
		return 1;
	}

	/* and this ensures the rows point to the correct place in the pixel data */
	for (i=0; i<p->h; ++i) {
		row_pointers[i] = p->pixels + (i * rb);
	}
	png_write_image(png_ptr, row_pointers);

	png_write_end(png_ptr, info_ptr);

	/* clean up */
	png_destroy_write_struct(&png_ptr, (png_infopp)NULL);

	fclose(f);

	return 0;
}
